package com.gitee.wsl.prism4j.languages

import com.gitee.wsl.prism4j.GrammarUtils
import com.gitee.wsl.prism4j.GrammarUtils.insertBeforeToken
import com.gitee.wsl.prism4j.GrammarUtils.require
import com.gitee.wsl.prism4j.Prism4j
import com.gitee.wsl.prism4j.Prism4j.Companion.grammar
import com.gitee.wsl.prism4j.Prism4j.Companion.pattern
import com.gitee.wsl.prism4j.Prism4j.Companion.token
import com.gitee.wsl.prism4j.annotations.Aliases
import com.gitee.wsl.prism4j.annotations.Extend
import com.gitee.wsl.prism4j.annotations.Modify
import com.gitee.wsl.prism4j.Prism4j.Pattern

@Suppress("unused")
@Aliases("js")
@Modify("markup")
@Extend("clike")
object Prism_javascript {
    fun create(prism4j: Prism4j): Prism4j.Grammar {
        val js: Prism4j.Grammar = GrammarUtils.extend(
            require(prism4j, "clike"), "javascript",
            token(
                "keyword",
                pattern(Pattern.compile("\\b(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|var|void|while|with|yield)\\b"))
            ),
            token(
                "number",
                pattern(Pattern.compile("\\b(?:0[xX][\\dA-Fa-f]+|0[bB][01]+|0[oO][0-7]+|NaN|Infinity)\\b|(?:\\b\\d+\\.?\\d*|\\B\\.\\d+)(?:[Ee][+-]?\\d+)?"))
            ),
            token(
                "function",
                pattern(
                    Pattern.compile(
                        "[_\$a-z\\xA0-\\uFFFF][$\\w\\xA0-\\uFFFF]*(?=\\s*\\()",
                        Pattern.CASE_INSENSITIVE
                    )
                )
            ),
            token(
                "operator",
                pattern(Pattern.compile("-[-=]?|\\+[+=]?|!=?=?|<<?=?|>>?>?=?|=(?:==?|>)?|&[&=]?|\\|[|=]?|\\*\\*?=?|\\/=?|~|\\^=?|%=?|\\?|\\.{3}"))
            )
        )

        insertBeforeToken(
            js, "keyword",
            token(
                "regex", pattern(
                    Pattern.compile("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s])\\s*)\\/(\\[[^\\]\\r\\n]+]|\\\\.|[^/\\\\\\[\\r\\n])+\\/[gimyu]{0,5}(?=\\s*($|[\\r\\n,.;})\\]]))"),
                    true,
                    true
                )
            ),
            token(
                "function-variable",
                pattern(
                    Pattern.compile(
                        "[_\$a-z\\xA0-\\uFFFF][$\\w\\xA0-\\uFFFF]*(?=\\s*=\\s*(?:function\\b|(?:\\([^()]*\\)|[_\$a-z\\xA0-\\uFFFF][$\\w\\xA0-\\uFFFF]*)\\s*=>))",
                        Pattern.CASE_INSENSITIVE
                    ),
                    false,
                    false,
                    "function"
                )
            ),
            token("constant", pattern(Pattern.compile("\\b[A-Z][A-Z\\d_]*\\b")))
        )

        val interpolation: Prism4j.Token = token("interpolation")

        insertBeforeToken(
            js, "string",
            token(
                "template-string",
                pattern(
                    Pattern.compile("`(?:\\\\[\\s\\S]|\\$\\{[^}]+\\}|[^\\\\`])*`"),
                    false,
                    true,
                    null,
                    grammar(
                        "inside",
                        interpolation,
                        token("string", pattern(Pattern.compile("[\\s\\S]+")))
                    )
                )
            )
        )

        val insideInterpolation: Prism4j.Grammar?
        run {
            val tokens: MutableList<Prism4j.Token> = ArrayList(js.tokens().size + 1)
            tokens.add(
                token(
                    "interpolation-punctuation",
                    pattern(Pattern.compile("^\\$\\{|\\}$"), false, false, "punctuation")
                )
            )
            tokens.addAll(js.tokens())
            insideInterpolation = grammar("inside", tokens)
        }

        interpolation.patterns().add(
            pattern(
                Pattern.compile("\\$\\{[^}]+\\}"),
                false,
                false,
                null,
                insideInterpolation
            )
        )

        val markup = prism4j.grammar("markup")
        if (markup != null) {
            insertBeforeToken(
                markup, "tag",
                token(
                    "script", pattern(
                        Pattern.compile("(<script[\\s\\S]*?>)[\\s\\S]*?(?=<\\/script>)", Pattern.CASE_INSENSITIVE),
                        true,
                        true,
                        "language-javascript",
                        js
                    )
                )
            )
        }

        return js
    }
}
